package oas

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"

	"github.com/TykTechnologies/storage/persistent/model"
	"github.com/TykTechnologies/tyk/apidef"
	"github.com/TykTechnologies/tyk/config"
	"github.com/TykTechnologies/tyk/internal/event"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/oasdiff/yaml"
	"github.com/stretchr/testify/assert"
)

func TestOAS(t *testing.T) {
	t.Parallel()

	t.Run("empty paths", func(t *testing.T) {
		t.Parallel()

		var emptyOASPaths OAS
		emptyOASPaths.Components = &openapi3.Components{}
		emptyOASPaths.Paths = openapi3.NewPaths()
		emptyOASPaths.SetTykExtension(&XTykAPIGateway{})

		var convertedAPI apidef.APIDefinition
		emptyOASPaths.ExtractTo(&convertedAPI)

		var resultOAS OAS
		resultOAS.Fill(convertedAPI)

		// This tests that zero-value extensions are cleared
		emptyOASPaths.Extensions = nil
		assert.Equal(t, emptyOASPaths, resultOAS)
	})

	t.Run("nil paths", func(t *testing.T) {
		t.Parallel()

		var nilOASPaths OAS
		nilOASPaths.Components = &openapi3.Components{}
		nilOASPaths.SetTykExtension(&XTykAPIGateway{})

		var convertedAPI apidef.APIDefinition
		nilOASPaths.ExtractTo(&convertedAPI)

		var resultOAS OAS
		resultOAS.Fill(convertedAPI)

		// No paths in base OAS produce empty paths{} when converted back
		nilOASPaths.Paths = openapi3.NewPaths()
		nilOASPaths.Extensions = nil
		assert.Equal(t, nilOASPaths, resultOAS)
	})

	t.Run("extract paths", func(t *testing.T) {
		const operationID = "userGET"
		t.Parallel()

		var oasWithPaths = OAS{
			T: openapi3.T{
				Components: &openapi3.Components{},
				Paths:      openapi3.NewPaths(),
			},
		}

		oasWithPaths.SetTykExtension(&XTykAPIGateway{
			Middleware: &Middleware{
				Operations: Operations{
					operationID: {
						Allow: &Allowance{
							Enabled: true,
						},
					},
				},
			},
		})
		oasWithPaths.Paths.Set("/user", &openapi3.PathItem{
			Get: &openapi3.Operation{
				OperationID: operationID,
				Responses:   openapi3.NewResponses(),
			},
		})

		var convertedAPI apidef.APIDefinition
		oasWithPaths.ExtractTo(&convertedAPI)

		var resultOAS OAS
		resultOAS.Fill(convertedAPI)

		assert.Equal(t, oasWithPaths, resultOAS)
	})

	t.Run("auth configs", func(t *testing.T) {
		t.Parallel()

		var api apidef.APIDefinition
		api.AuthConfigs = make(map[string]apidef.AuthConfig)

		a := apidef.AuthConfig{}
		Fill(t, &a, 0)
		api.AuthConfigs[apidef.AuthTokenType] = a

		sw := &OAS{}
		sw.Fill(api)

		var converted apidef.APIDefinition
		sw.ExtractTo(&converted)

		assert.Equal(t, api.AuthConfigs, converted.AuthConfigs)
	})
}

func TestOAS_ExtractTo_DontTouchExistingClassicFields(t *testing.T) {
	var api apidef.APIDefinition
	api.VersionData.Versions = map[string]apidef.VersionInfo{
		Main: {
			ExtendedPaths: apidef.ExtendedPathsSet{
				PersistGraphQL: []apidef.PersistGraphQLMeta{
					{},
				},
			},
		},
	}

	var s OAS
	s.ExtractTo(&api)

	assert.Len(t, api.VersionData.Versions[Main].ExtendedPaths.PersistGraphQL, 1)
}

func TestOAS_ExtractTo_ResetAPIDefinition(t *testing.T) {
	var a apidef.APIDefinition
	Fill(t, &a, 0)

	// Fill doesn't populate eventhandlers to a valid value, we do it now.
	a.EventHandlers.Events = map[apidef.TykEvent][]apidef.EventHandlerTriggerConfig{
		event.QuotaExceeded: {
			{
				Handler: event.WebHookHandler,
				HandlerMeta: map[string]any{
					"target_path": "https://webhook.site/uuid",
				},
			},
			{
				Handler: event.JSVMHandler,
				HandlerMeta: map[string]any{
					"name": "myHandler",
					"path": "my_script.js",
				},
			},
			{
				Handler: event.LogHandler,
				HandlerMeta: map[string]any{
					"prefix": "QuotaExceededEvent",
				},
			},
		},
	}

	var vInfo apidef.VersionInfo
	Fill(t, &vInfo, 0)
	a.VersionData.Versions = map[string]apidef.VersionInfo{
		Main: vInfo,
	}

	var s OAS
	s.ExtractTo(&a)

	a.UseKeylessAccess = false
	a.UpstreamCertificatesDisabled = false
	a.CertificatePinningDisabled = false
	a.Proxy.ServiceDiscovery.CacheDisabled = false
	a.CustomMiddlewareBundleDisabled = false
	a.DomainDisabled = false
	a.ConfigDataDisabled = false
	a.CustomMiddleware.AuthCheck.Disabled = false
	a.CustomMiddleware.IdExtractor.Disabled = false
	a.GlobalRateLimit.Disabled = false
	a.TagsDisabled = false
	a.IsOAS = false
	a.IDPClientIDMappingDisabled = false
	a.EnableContextVars = false
	a.DoNotTrack = false
	a.IPAccessControlDisabled = false
	a.UptimeTests.Disabled = false

	// deprecated fields
	a.Auth = apidef.AuthConfig{}
	a.VersionDefinition.StripPath = false
	a.UseGoPluginAuth = false
	a.EnableCoProcessAuth = false
	a.JWTScopeToPolicyMapping = nil
	a.JWTScopeClaimName = ""
	a.VersionData.NotVersioned = false
	vInfo = a.VersionData.Versions[""]
	vInfo.Name = ""
	vInfo.Expires = ""
	vInfo.Paths.Ignored = nil
	vInfo.Paths.WhiteList = nil
	vInfo.Paths.BlackList = nil
	vInfo.OverrideTarget = ""
	vInfo.GlobalHeadersDisabled = false
	vInfo.GlobalResponseHeadersDisabled = false
	vInfo.UseExtendedPaths = false
	vInfo.GlobalSizeLimitDisabled = false

	vInfo.ExtendedPaths.Clear()

	a.VersionData.Versions[""] = vInfo

	a.UptimeTests.Config.ServiceDiscovery.CacheDisabled = false

	assert.Empty(t, a.Name)

	noOASSupportFields := getNonEmptyFields(a, "APIDefinition")

	// The expectedFields value lists fields that do not support migration.
	// When adding a migration for ExtendedPaths sections, clear the list of
	// fields below, and clear the value in ExtendedPaths.Clear() function.

	expectedFields := []string{
		"APIDefinition.Slug",
		"APIDefinition.EnableProxyProtocol",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.TransformJQ[0].Filter",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.TransformJQ[0].Path",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.TransformJQ[0].Method",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.TransformJQResponse[0].Filter",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.TransformJQResponse[0].Path",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.TransformJQResponse[0].Method",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.PersistGraphQL[0].Path",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.PersistGraphQL[0].Method",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.PersistGraphQL[0].Operation",
		"APIDefinition.VersionData.Versions[0].ExtendedPaths.PersistGraphQL[0].Variables[0]",
		"APIDefinition.AuthProvider.Name",
		"APIDefinition.AuthProvider.StorageEngine",
		"APIDefinition.AuthProvider.Meta[0]",
		"APIDefinition.SessionProvider.Name",
		"APIDefinition.SessionProvider.StorageEngine",
		"APIDefinition.SessionProvider.Meta[0]",
		"APIDefinition.EnableIpWhiteListing",
		"APIDefinition.EnableIpBlacklisting",
		"APIDefinition.ResponseProcessors[0].Name",
		"APIDefinition.ResponseProcessors[0].Options",
		"APIDefinition.GraphQL.Enabled",
		"APIDefinition.GraphQL.ExecutionMode",
		"APIDefinition.GraphQL.Version",
		"APIDefinition.GraphQL.Schema",
		"APIDefinition.GraphQL.TypeFieldConfigurations[0].TypeName",
		"APIDefinition.GraphQL.TypeFieldConfigurations[0].FieldName",
		"APIDefinition.GraphQL.TypeFieldConfigurations[0].Mapping.Disabled",
		"APIDefinition.GraphQL.TypeFieldConfigurations[0].Mapping.Path",
		"APIDefinition.GraphQL.TypeFieldConfigurations[0].DataSource.Name",
		"APIDefinition.GraphQL.TypeFieldConfigurations[0].DataSource.Config[0]",
		"APIDefinition.GraphQL.GraphQLPlayground.Enabled",
		"APIDefinition.GraphQL.GraphQLPlayground.Path",
		"APIDefinition.GraphQL.Engine.FieldConfigs[0].TypeName",
		"APIDefinition.GraphQL.Engine.FieldConfigs[0].FieldName",
		"APIDefinition.GraphQL.Engine.FieldConfigs[0].DisableDefaultMapping",
		"APIDefinition.GraphQL.Engine.FieldConfigs[0].Path[0]",
		"APIDefinition.GraphQL.Engine.DataSources[0].Kind",
		"APIDefinition.GraphQL.Engine.DataSources[0].Name",
		"APIDefinition.GraphQL.Engine.DataSources[0].Internal",
		"APIDefinition.GraphQL.Engine.DataSources[0].RootFields[0].Type",
		"APIDefinition.GraphQL.Engine.DataSources[0].RootFields[0].Fields[0]",
		"APIDefinition.GraphQL.Engine.DataSources[0].Config[0]",
		"APIDefinition.GraphQL.Engine.GlobalHeaders[0].Key",
		"APIDefinition.GraphQL.Engine.GlobalHeaders[0].Value",
		"APIDefinition.GraphQL.Proxy.Features.UseImmutableHeaders",
		"APIDefinition.GraphQL.Proxy.AuthHeaders[0]",
		"APIDefinition.GraphQL.Proxy.SubscriptionType",
		"APIDefinition.GraphQL.Proxy.SSEUsePost",
		"APIDefinition.GraphQL.Proxy.RequestHeaders[0]",
		"APIDefinition.GraphQL.Proxy.UseResponseExtensions.OnErrorForwarding",
		"APIDefinition.GraphQL.Proxy.RequestHeadersRewrite[0].Value",
		"APIDefinition.GraphQL.Proxy.RequestHeadersRewrite[0].Remove",
		"APIDefinition.GraphQL.Subgraph.SDL",
		"APIDefinition.GraphQL.Supergraph.Subgraphs[0].APIID",
		"APIDefinition.GraphQL.Supergraph.Subgraphs[0].Name",
		"APIDefinition.GraphQL.Supergraph.Subgraphs[0].URL",
		"APIDefinition.GraphQL.Supergraph.Subgraphs[0].SDL",
		"APIDefinition.GraphQL.Supergraph.Subgraphs[0].Headers[0]",
		"APIDefinition.GraphQL.Supergraph.Subgraphs[0].SubscriptionType",
		"APIDefinition.GraphQL.Supergraph.Subgraphs[0].SSEUsePost",
		"APIDefinition.GraphQL.Supergraph.MergedSDL",
		"APIDefinition.GraphQL.Supergraph.GlobalHeaders[0]",
		"APIDefinition.GraphQL.Supergraph.DisableQueryBatching",
		"APIDefinition.GraphQL.Introspection.Disabled",
		"APIDefinition.AnalyticsPlugin.Enabled",
		"APIDefinition.AnalyticsPlugin.PluginPath",
		"APIDefinition.AnalyticsPlugin.FuncName",
		"APIDefinition.SecurityRequirements[0]",
	}

	assert.Equal(t, expectedFields, noOASSupportFields)
}

func TestOAS_AddServers(t *testing.T) {
	t.Parallel()
	type fields struct {
		T openapi3.T
	}
	type args struct {
		apiURLs []string
	}

	tests := []struct {
		name            string
		fields          fields
		args            args
		expectedServers openapi3.Servers
	}{
		{
			name:   "empty servers",
			fields: fields{T: openapi3.T{}},
			args:   args{apiURLs: []string{"http://127.0.0.1:8080/api"}},
			expectedServers: openapi3.Servers{
				{URL: "http://127.0.0.1:8080/api"},
			},
		},
		{
			name:   "empty servers and named parameters with regex",
			fields: fields{T: openapi3.T{}},
			args:   args{apiURLs: []string{"http://{subdomain:[a-z]+}/api"}},
			expectedServers: openapi3.Servers{
				{URL: "http://{subdomain}/api", Variables: map[string]*openapi3.ServerVariable{
					"subdomain": {
						Default: "pathParam1",
					},
				}},
			},
		},
		{
			name: "non-empty servers",
			fields: fields{T: openapi3.T{
				Servers: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
				},
			}},
			args: args{apiURLs: []string{"http://127.0.0.1:8080/api"}},
			expectedServers: openapi3.Servers{
				{URL: "http://127.0.0.1:8080/api"},
				{URL: "http://example-upstream.org/api"},
			},
		},
		{
			name: "non-empty servers and mix on named parameters and normal urls",
			fields: fields{T: openapi3.T{
				Servers: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
				},
			}},
			args: args{apiURLs: []string{"http://127.0.0.1:8080/api", "http://{subdomain}/api/{version:v\\d+}"}},
			expectedServers: openapi3.Servers{
				{URL: "http://127.0.0.1:8080/api"},
				{URL: "http://example-upstream.org/api"},
				{URL: "http://{subdomain}/api/{version}", Variables: map[string]*openapi3.ServerVariable{
					"subdomain": {Default: "pathParam1"},
					"version":   {Default: "pathParam2"},
				}},
			},
		},
		{
			name: "non-empty servers having same URL that of apiURL",
			fields: fields{T: openapi3.T{
				Servers: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
					{
						URL: "http://legacy-upstream.org/api",
					},
					{
						URL: "http://127.0.0.1:8080/api",
					},
				},
			}},
			args: args{apiURLs: []string{"http://127.0.0.1:8080/api"}},
			expectedServers: openapi3.Servers{
				{URL: "http://127.0.0.1:8080/api"},
				{URL: "http://example-upstream.org/api"},
				{URL: "http://legacy-upstream.org/api"},
			},
		},
		{
			name: "non-empty servers having same URL that of apiURL",
			fields: fields{T: openapi3.T{
				Servers: openapi3.Servers{
					{
						URL: "http://127.0.0.1:8080/api",
					},
					{
						URL: "http://example-upstream.org/api",
					},
				},
			}},
			args: args{apiURLs: []string{"http://127.0.0.1:8080/api"}},
			expectedServers: openapi3.Servers{
				{URL: "http://127.0.0.1:8080/api"},
				{URL: "http://example-upstream.org/api"},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := &OAS{T: tt.fields.T}
			err := s.AddServers(tt.args.apiURLs...)

			assert.NoError(t, err)
			assert.ElementsMatch(t, tt.expectedServers, s.Servers)
		})
	}
}

func TestOAS_UpdateServers(t *testing.T) {
	t.Parallel()
	type fields struct {
		S openapi3.Servers
	}
	type args struct {
		apiURL    string
		oldAPIURL string
	}
	tests := []struct {
		name            string
		fields          fields
		args            args
		expectedServers openapi3.Servers
	}{
		{
			name:   "empty servers",
			fields: fields{S: openapi3.Servers{}},
			args:   args{apiURL: "http://127.0.0.1:8080/api", oldAPIURL: ""},
			expectedServers: openapi3.Servers{
				{
					URL: "http://127.0.0.1:8080/api",
				},
			},
		},
		{
			name: "non-empty servers replace with new",
			fields: fields{
				S: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
				},
			},
			args: args{apiURL: "http://127.0.0.1:8080/api", oldAPIURL: "http://example-upstream.org/api"},
			expectedServers: openapi3.Servers{
				{
					URL: "http://127.0.0.1:8080/api",
				},
			},
		},
		{
			name: "non-empty servers not replace",
			fields: fields{
				S: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
				},
			},
			args: args{apiURL: "http://127.0.0.1:8080/api", oldAPIURL: "http://localhost/api"},
			expectedServers: openapi3.Servers{
				{
					URL: "http://example-upstream.org/api",
				},
			},
		},
		{
			name: "apiURL with named parameter, do not add to existing servers(not added by Tyk)",
			fields: fields{
				S: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
				},
			},
			args: args{apiURL: "http://{subdomain:[a-z]+}/api", oldAPIURL: "http://localhost/api"},
			expectedServers: openapi3.Servers{
				{
					URL: "http://example-upstream.org/api",
				},
			},
		},
		{
			name: "apiURL with named parameter, remove servers entry added by Tyk",
			fields: fields{
				S: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
					{
						URL: "http://other-upstream.org/api",
					},
				},
			},
			args: args{apiURL: "http://{subdomain:[a-z]+}/api", oldAPIURL: "http://example-upstream.org/api"},
			expectedServers: openapi3.Servers{
				{
					URL: "http://other-upstream.org/api",
				},
			},
		},
		{
			name: "apiURL with named parameter, remove only servers entry added by Tyk",
			fields: fields{
				S: openapi3.Servers{
					{
						URL: "http://example-upstream.org/api",
					},
				},
			},
			args:            args{apiURL: "http://{subdomain:[a-z]+}/api", oldAPIURL: "http://example-upstream.org/api"},
			expectedServers: openapi3.Servers{},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := &OAS{
				T: openapi3.T{Servers: tt.fields.S},
			}
			s.UpdateServers(tt.args.apiURL, tt.args.oldAPIURL)

			assert.Equal(t, tt.expectedServers, s.Servers)
		})
	}
}

func TestOAS_ReplaceServers(t *testing.T) {
	t.Parallel()
	type fields struct {
		T openapi3.T
	}

	type args struct {
		apiURLs    []string
		oldAPIURLs []string
	}

	tests := []struct {
		name               string
		fields             fields
		args               args
		expectedServerURls []string
	}{
		{
			name:               "empty servers",
			fields:             fields{T: openapi3.T{}},
			args:               args{apiURLs: []string{"http://127.0.0.1:8080/api"}, oldAPIURLs: nil},
			expectedServerURls: []string{"http://127.0.0.1:8080/api"},
		},
		{
			name: "non-empty servers - remove old and add new",
			fields: fields{T: openapi3.T{
				Servers: openapi3.Servers{
					{
						URL: "http://tyk.gateway-1.com/api",
					},
					{
						URL: "http://tyk.gateway-2.com/api",
					},
				},
			}},
			args: args{apiURLs: []string{"http://tyk.gateway-4.com/api", "http://tyk.gateway-2.com/api"},
				oldAPIURLs: []string{"http://tyk.gateway-1.com/api", "http://tyk.gateway-2.com/api"}},
			expectedServerURls: []string{"http://tyk.gateway-4.com/api", "http://tyk.gateway-2.com/api"},
		},
		{
			name: "non-empty servers - remove old and add new, retain userAdded ones",
			fields: fields{T: openapi3.T{
				Servers: openapi3.Servers{
					{
						URL: "http://tyk.gateway-1.com/api",
					},
					{
						URL: "http://tyk.gateway-2.com/api",
					},
					{
						URL: "http://upstream.org/api",
					},
				},
			}},
			args: args{apiURLs: []string{"http://tyk.gateway-4.com/api", "http://tyk.gateway-2.com/api"},
				oldAPIURLs: []string{"http://tyk.gateway-1.com/api", "http://tyk.gateway-2.com/api"}},
			expectedServerURls: []string{"http://tyk.gateway-4.com/api", "http://tyk.gateway-2.com/api", "http://upstream.org/api"},
		},
		{
			name: "retain user added servers",
			fields: fields{T: openapi3.T{
				Servers: openapi3.Servers{
					{
						URL: "http://upstream.org/api",
					},
				},
			}},
			args: args{apiURLs: []string{"http://tyk.gateway-4.com/api", "http://tyk.gateway-2.com/api"},
				oldAPIURLs: []string{"http://tyk.gateway-1.com/api", "http://tyk.gateway-2.com/api"}},
			expectedServerURls: []string{"http://tyk.gateway-4.com/api", "http://tyk.gateway-2.com/api", "http://upstream.org/api"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := &OAS{
				T: tt.fields.T,
			}
			s.ReplaceServers(tt.args.apiURLs, tt.args.oldAPIURLs)
			var serverURLs []string
			for _, server := range s.Servers {
				serverURLs = append(serverURLs, server.URL)
			}
			assert.Equal(t, tt.expectedServerURls, serverURLs)
		})
	}
}

func TestOAS_GetSecuritySchemes(t *testing.T) {
	token := Token{}
	Fill(t, &token, 0)

	jwt := JWT{}
	Fill(t, &jwt, 0)
	// set the customClaims value to float if it is an int, it is reconverted to float on unmarshal since the field is an interface{}
	for _, conf := range jwt.CustomClaimValidation {
		for i, v := range conf.AllowedValues {
			iv, ok := v.(int)
			if ok {
				conf.AllowedValues[i] = float64(iv)
			}
		}
	}

	oauth := OAuth{}
	Fill(t, &oauth, 0)

	basic := Basic{}
	Fill(t, &basic, 0)

	expectedSS := SecuritySchemes{
		"my_auth":  &token,
		"my_jwt":   &jwt,
		"my_oauth": &oauth,
		"my_basic": &basic,
	}

	oas := OAS{}
	xTykAPIGateway := XTykAPIGateway{
		Server: Server{
			Authentication: &Authentication{
				SecuritySchemes: expectedSS,
			},
		},
	}

	oas.SetTykExtension(&xTykAPIGateway)

	oasInBytes, err := json.Marshal(&oas)
	assert.NoError(t, err)

	var resOAS OAS
	err = json.Unmarshal(oasInBytes, &resOAS)
	assert.NoError(t, err)

	assert.Equal(t, &token, resOAS.getTykTokenAuth("my_auth"))
	assert.Equal(t, &jwt, resOAS.getTykJWTAuth("my_jwt"))
	assert.Equal(t, &basic, resOAS.getTykBasicAuth("my_basic"))
	assert.Equal(t, &oauth, resOAS.getTykOAuthAuth("my_oauth"))
}

func Test_toStructIfMap(t *testing.T) {
	token := &Token{}
	Fill(t, token, 0)

	resToken := &Token{}
	toStructIfMap(token, resToken)
	assert.Equal(t, &Token{}, resToken)

	tokenInBytes, _ := json.Marshal(token)

	var mapToken map[string]interface{}
	_ = json.Unmarshal(tokenInBytes, &mapToken)

	toStructIfMap(mapToken, resToken)

	assert.Equal(t, token, resToken)
}

func TestOAS_MarshalJSON(t *testing.T) {
	t.Run("nil license and extenalDocs", func(t *testing.T) {
		s := &OAS{
			T: openapi3.T{
				Info: &openapi3.Info{
					License: &openapi3.License{},
				},
				ExternalDocs: &openapi3.ExternalDocs{},
			},
		}

		inBytes, err := json.Marshal(s)
		assert.NoError(t, err)

		assert.NotContains(t, string(inBytes), "license")
		assert.NotContains(t, string(inBytes), "externalDocs")
	})

	t.Run("should not base64 encode extension values when it's slice of bytes", func(t *testing.T) {
		s := OAS{
			openapi3.T{
				Info: &openapi3.Info{
					Title: "OAS Doc",
				},
				Extensions: map[string]interface{}{
					ExtensionTykAPIGateway: XTykAPIGateway{
						Info: Info{
							Name: "OAS API",
						},
					},
				},
			},
		}

		t.Run("int", func(t *testing.T) {
			copyOAS := s
			intVal := 9
			byteRep, _ := json.Marshal(intVal)
			copyOAS.Extensions["x-abcd"] = byteRep

			data, err := copyOAS.MarshalJSON()
			assert.NoError(t, err)
			assert.Contains(t, string(data), `"x-abcd":9`)
		})

		t.Run("float", func(t *testing.T) {
			copyOAS := s
			floatVal := 9.5
			byteRep, _ := json.Marshal(floatVal)
			copyOAS.Extensions["x-abcd"] = byteRep

			data, err := copyOAS.MarshalJSON()
			assert.NoError(t, err)
			assert.Contains(t, string(data), `"x-abcd":9.5`)
		})

		t.Run("bool", func(t *testing.T) {
			copyOAS := s
			boolVal := false
			byteRep, _ := json.Marshal(boolVal)
			copyOAS.Extensions["x-abcd"] = byteRep

			data, err := copyOAS.MarshalJSON()
			assert.NoError(t, err)
			assert.Contains(t, string(data), `"x-abcd":false`)
		})

		t.Run("nil", func(t *testing.T) {
			copyOAS := s
			copyOAS.Extensions["x-abcd"] = nil

			data, err := copyOAS.MarshalJSON()
			assert.NoError(t, err)
			assert.Contains(t, string(data), `"x-abcd":null`)
		})

		t.Run("string", func(t *testing.T) {
			copyOAS := s
			copyOAS.Extensions["x-abcd"] = []byte(`"hello"`)

			data, err := copyOAS.MarshalJSON()
			assert.NoError(t, err)
			assert.Contains(t, string(data), `"x-abcd":"hello"`)
		})

		t.Run("map", func(t *testing.T) {
			copyOAS := s
			copyOAS.Extensions["x-abcd"] = []byte(`{"key":"value"}`)

			data, err := copyOAS.MarshalJSON()
			assert.NoError(t, err)
			assert.Contains(t, string(data), `"x-abcd":{"key":"value"}`)
		})

		t.Run("array", func(t *testing.T) {
			copyOAS := s
			copyOAS.Extensions["x-abcd"] = []byte(`[{"key":"value"},{"key":"value"}]`)

			data, err := copyOAS.MarshalJSON()
			assert.NoError(t, err)
			assert.Contains(t, string(data), `"x-abcd":[{"key":"value"},{"key":"value"}]`)
		})
	})
}

func TestOAS_Clone(t *testing.T) {
	s := &OAS{}
	s.SetTykExtension(&XTykAPIGateway{Info: Info{
		Name: "my-api",
	}})

	clonedOAS, err := s.Clone()
	assert.NoError(t, err)
	assert.Equal(t, s, clonedOAS)

	s.GetTykExtension().Info.Name = "my-api-modified"
	assert.NotEqual(t, s, clonedOAS)

	t.Run("clone impossible to marshal value", func(t *testing.T) {
		s.Extensions["weird extension"] = make(chan int)

		result, err := s.Clone()
		assert.NoError(t, err)

		_, ok := result.Extensions["weird extension"]
		assert.True(t, ok)
	})
}

func BenchmarkOAS_Clone(b *testing.B) {
	oas := &OAS{
		T: openapi3.T{
			Info: &openapi3.Info{
				Title: "my-oas-doc",
			},
			Paths: func() *openapi3.Paths {
				paths := openapi3.NewPaths()
				paths.Set("/get", &openapi3.PathItem{
					Get: &openapi3.Operation{
						Responses: func() *openapi3.Responses {
							responses := openapi3.NewResponses()
							responses.Set("200", &openapi3.ResponseRef{
								Value: &openapi3.Response{
									Description: getStrPointer("some example endpoint"),
								},
							})
							return responses
						}(),
					},
				})
				return paths
			}(),
		},
	}

	for i := 0; i < b.N; i++ {
		_, err := oas.Clone()
		assert.NoError(b, err)
	}
}

func TestMigrateAndFillOAS(t *testing.T) {
	var api apidef.APIDefinition
	api.SetDisabledFlags()
	api.Name = "Furkan"
	api.Proxy.ListenPath = "/furkan"
	api.VersionDefinition.Key = apidef.DefaultAPIVersionKey
	api.VersionDefinition.Location = apidef.HeaderLocation
	api.VersionData.DefaultVersion = "Default"
	api.VersionData.Versions = map[string]apidef.VersionInfo{
		"Default": {},
		"v1":      {},
		"v2":      {},
	}

	baseAPIDef, versionAPIDefs, err := MigrateAndFillOAS(&api)
	assert.NoError(t, err)
	assert.Len(t, versionAPIDefs, 2)
	assert.True(t, baseAPIDef.Classic.IsOAS)
	assert.Equal(t, DefaultOpenAPI, baseAPIDef.OAS.OpenAPI)
	assert.Equal(t, "Furkan", baseAPIDef.OAS.Info.Title)
	assert.Equal(t, "Default", baseAPIDef.OAS.Info.Version)

	assert.True(t, versionAPIDefs[0].Classic.IsOAS)
	assert.Equal(t, DefaultOpenAPI, versionAPIDefs[0].OAS.OpenAPI)
	assert.Equal(t, "Furkan-v1", versionAPIDefs[0].OAS.Info.Title)
	assert.Equal(t, "v1", versionAPIDefs[0].OAS.Info.Version)

	assert.True(t, versionAPIDefs[1].Classic.IsOAS)
	assert.Equal(t, DefaultOpenAPI, versionAPIDefs[1].OAS.OpenAPI)
	assert.Equal(t, "Furkan-v2", versionAPIDefs[1].OAS.Info.Title)
	assert.Equal(t, "v2", versionAPIDefs[1].OAS.Info.Version)

	err = baseAPIDef.OAS.Validate(context.Background())
	assert.NoError(t, err)

	t.Run("migration fails", func(t *testing.T) {
		_, _, err = MigrateAndFillOAS(&api)
		assert.ErrorIs(t, err, apidef.ErrMigrationNewVersioningEnabled)
	})

	t.Run("migrated base API validation fails", func(t *testing.T) {
		api = apidef.APIDefinition{Name: "Furkan"}
		_, _, err = MigrateAndFillOAS(&api)
		assert.ErrorContains(t, err, "base API Furkan migrated OAS is not valid")
	})

	t.Run("migrate versionAPI validation fails", func(t *testing.T) {
		api = apidef.APIDefinition{}
		api.SetDisabledFlags()
		api.Name = "Furkan"
		api.Proxy.ListenPath = "/furkan"
		api.VersionDefinition.Key = apidef.DefaultAPIVersionKey
		api.VersionDefinition.Location = apidef.HeaderLocation
		api.VersionData.DefaultVersion = "Default"
		api.VersionData.Versions = map[string]apidef.VersionInfo{
			"Default": {},
			"v2": {
				UseExtendedPaths: true,
				ExtendedPaths: apidef.ExtendedPathsSet{
					WhiteList: []apidef.EndPointMeta{
						{
							Disabled: false,
							Path:     "123",
							MethodActions: map[string]apidef.EndpointMethodMeta{
								http.MethodGet: {},
							},
						},
					},
				}},
		}
		_, _, err = MigrateAndFillOAS(&api)
		assert.ErrorContains(t, err, "version API Furkan-v2 migrated OAS is not valid")
	})
}

func TestMigrateAndFillOAS_DropEmpties(t *testing.T) {
	api := apidef.APIDefinition{Name: "Furkan"}
	api.Proxy.ListenPath = "/furkan"

	api.VersionDefinition.Location = apidef.HeaderLocation
	api.VersionDefinition.Key = apidef.DefaultAPIVersionKey
	api.VersionData.NotVersioned = true
	api.VersionData.Versions = map[string]apidef.VersionInfo{
		"Default": {},
	}
	api.ConfigDataDisabled = true

	baseAPI, _, err := MigrateAndFillOAS(&api)
	assert.NoError(t, err)

	t.Run("versioning", func(t *testing.T) {
		assert.Nil(t, baseAPI.OAS.GetTykExtension().Info.Versioning)
	})

	t.Run("plugin bundle", func(t *testing.T) {
		assert.Equal(t, &Middleware{
			Global: &Global{
				TrafficLogs: &TrafficLogs{
					Enabled: true,
				},
			},
		}, baseAPI.OAS.GetTykExtension().Middleware)
	})

	t.Run("mutualTLS", func(t *testing.T) {
		assert.Nil(t, baseAPI.OAS.GetTykExtension().Upstream.MutualTLS)
	})

	t.Run("certificatePinning", func(t *testing.T) {
		assert.Nil(t, baseAPI.OAS.GetTykExtension().Upstream.CertificatePinning)
	})

	t.Run("gatewayTags", func(t *testing.T) {
		assert.Nil(t, baseAPI.OAS.GetTykExtension().Server.GatewayTags)
	})

	t.Run("customDomain", func(t *testing.T) {
		assert.Nil(t, baseAPI.OAS.GetTykExtension().Server.CustomDomain)
	})
}

func TestMigrateAndFillOAS_ValidateRequest(t *testing.T) {
	newValidateJSONAPI := func(schema map[string]interface{}) *apidef.APIDefinition {
		return &apidef.APIDefinition{
			Name:  "my-api",
			Proxy: apidef.ProxyConfig{ListenPath: "/listen"},
			VersionData: apidef.VersionData{
				NotVersioned: true,
				Versions: map[string]apidef.VersionInfo{
					"Default": {
						UseExtendedPaths: true,
						ExtendedPaths: apidef.ExtendedPathsSet{
							ValidateJSON: []apidef.ValidatePathMeta{
								{
									Method:            http.MethodPost,
									Path:              "/post",
									Schema:            schema,
									ErrorResponseCode: http.StatusTeapot,
								},
							},
						},
					},
				},
			},
			ConfigDataDisabled: true,
		}
	}

	migratedAPI, _, err := MigrateAndFillOAS(newValidateJSONAPI(map[string]interface{}{"title": "Furkan"}))
	assert.NoError(t, err)

	pathItem := migratedAPI.OAS.Paths.Find("/post")
	assert.NotNil(t, pathItem)
	operation := pathItem.GetOperation(http.MethodPost)
	assert.NotNil(t, operation)
	assert.Equal(t, operation.RequestBody.Value.Content.Get("application/json").Schema.Value.Title, "Furkan")

	expectedValidateRequest := &ValidateRequest{
		Enabled:           true,
		ErrorResponseCode: http.StatusTeapot,
	}
	assert.Equal(t, expectedValidateRequest, migratedAPI.OAS.GetTykExtension().Middleware.Operations[operation.OperationID].ValidateRequest)
	assert.Nil(t, migratedAPI.Classic.VersionData.Versions[Main].ExtendedPaths.ValidateJSON)

	t.Run("fail", func(t *testing.T) {
		migratedAPI, _, err = MigrateAndFillOAS(newValidateJSONAPI(map[string]interface{}{"minLength": -1}))
		assert.Error(t, err)
	})
}

func TestMigrateAndFillOAS_CustomPluginAuth(t *testing.T) {
	t.Run("goplugin", func(t *testing.T) {
		api := apidef.APIDefinition{
			Name: "Custom plugin Auth",
			Proxy: apidef.ProxyConfig{
				ListenPath: "/",
			},
			UseGoPluginAuth: true,
			CustomMiddleware: apidef.MiddlewareSection{
				Driver: apidef.GoPluginDriver,
				AuthCheck: apidef.MiddlewareDefinition{
					Name: "AuthFunc",
					Path: "/path/to/plugin",
				},
				IdExtractor: apidef.MiddlewareIdExtractor{Disabled: true},
			},
			VersionData: apidef.VersionData{
				NotVersioned: true,
				Versions:     map[string]apidef.VersionInfo{},
			},
			ConfigDataDisabled: true,
		}
		migratedAPI, _, err := MigrateAndFillOAS(&api)
		assert.NoError(t, err)

		expectedAuthentication := Authentication{
			Enabled: true,
			Custom: &CustomPluginAuthentication{
				Enabled: true,
				Config: &AuthenticationPlugin{
					Enabled:      true,
					FunctionName: "AuthFunc",
					Path:         "/path/to/plugin",
				},
			},
		}

		assert.Equal(t, expectedAuthentication, *migratedAPI.OAS.GetTykExtension().Server.Authentication)
		assert.Equal(t, apidef.GoPluginDriver, migratedAPI.OAS.GetTykExtension().Middleware.Global.PluginConfig.Driver)
	})
	t.Run("coprocess", func(t *testing.T) {
		api := apidef.APIDefinition{
			Name: "Custom plugin Auth",
			Proxy: apidef.ProxyConfig{
				ListenPath: "/",
			},
			EnableCoProcessAuth: true,
			CustomMiddleware: apidef.MiddlewareSection{
				Driver: apidef.PythonDriver,
				AuthCheck: apidef.MiddlewareDefinition{
					Name: "AuthFunc",
					Path: "/path/to/plugin",
				},
				IdExtractor: apidef.MiddlewareIdExtractor{Disabled: true},
			},
			VersionData: apidef.VersionData{
				NotVersioned: true,
				Versions:     map[string]apidef.VersionInfo{},
			},
			AuthConfigs: map[string]apidef.AuthConfig{
				apidef.CoprocessType: {
					AuthHeaderName: "Authorization",
				},
			},
			ConfigDataDisabled: true,
		}
		migratedAPI, _, err := MigrateAndFillOAS(&api)
		assert.NoError(t, err)

		expectedAuthentication := Authentication{
			Enabled: true,
			Custom: &CustomPluginAuthentication{
				Enabled: true,
				Config: &AuthenticationPlugin{
					Enabled:      true,
					FunctionName: "AuthFunc",
					Path:         "/path/to/plugin",
				},
				AuthSources: AuthSources{
					Header: &AuthSource{
						Enabled: true,
						Name:    "Authorization",
					},
				},
			},
		}

		assert.Equal(t, expectedAuthentication, *migratedAPI.OAS.GetTykExtension().Server.Authentication)
		assert.Equal(t, apidef.PythonDriver, migratedAPI.OAS.GetTykExtension().Middleware.Global.PluginConfig.Driver)
	})
}

func TestMigrateAndFillOAS_CustomPlugins(t *testing.T) {
	t.Parallel()
	t.Run("pre", func(t *testing.T) {
		t.Parallel()
		api := apidef.APIDefinition{
			Name: "Custom plugin-pre",
			Proxy: apidef.ProxyConfig{
				ListenPath: "/",
			},
			CustomMiddleware: apidef.MiddlewareSection{
				Driver: apidef.GoPluginDriver,
				Pre: []apidef.MiddlewareDefinition{
					{
						Name: "Pre",
						Path: "/path/to/plugin",
					},
				},
			},
			VersionData: apidef.VersionData{
				NotVersioned: true,
				Versions:     map[string]apidef.VersionInfo{},
			},
			ConfigDataDisabled: true,
		}
		migratedAPI, _, err := MigrateAndFillOAS(&api)
		assert.NoError(t, err)

		expectedPrePlugin := CustomPlugins{
			{
				Enabled:      true,
				FunctionName: "Pre",
				Path:         "/path/to/plugin",
			},
		}
		assert.Equal(t, expectedPrePlugin, migratedAPI.OAS.GetTykExtension().Middleware.Global.PrePlugins)
		assert.Nil(t, migratedAPI.OAS.GetTykExtension().Middleware.Global.PrePlugin)
		assert.Equal(t, apidef.GoPluginDriver, migratedAPI.OAS.GetTykExtension().Middleware.Global.PluginConfig.Driver)
	})

	t.Run("postAuth", func(t *testing.T) {
		t.Parallel()
		api := apidef.APIDefinition{
			Name: "Custom plugin - post auth",
			Proxy: apidef.ProxyConfig{
				ListenPath: "/",
			},
			CustomMiddleware: apidef.MiddlewareSection{
				Driver: apidef.GoPluginDriver,
				PostKeyAuth: []apidef.MiddlewareDefinition{
					{
						Name: "PostAuth",
						Path: "/path/to/plugin",
					},
				},
			},
			VersionData: apidef.VersionData{
				NotVersioned: true,
				Versions:     map[string]apidef.VersionInfo{},
			},
			ConfigDataDisabled: true,
		}
		migratedAPI, _, err := MigrateAndFillOAS(&api)
		assert.NoError(t, err)

		expectedPrePlugin := CustomPlugins{
			{
				Enabled:      true,
				FunctionName: "PostAuth",
				Path:         "/path/to/plugin",
			},
		}
		assert.Equal(t, expectedPrePlugin, migratedAPI.OAS.GetTykExtension().Middleware.Global.PostAuthenticationPlugins)
		assert.Nil(t, migratedAPI.OAS.GetTykExtension().Middleware.Global.PostAuthenticationPlugin)
		assert.Equal(t, apidef.GoPluginDriver, migratedAPI.OAS.GetTykExtension().Middleware.Global.PluginConfig.Driver)
	})

	t.Run("post", func(t *testing.T) {
		t.Parallel()
		api := apidef.APIDefinition{
			Name: "Custom plugin - post",
			Proxy: apidef.ProxyConfig{
				ListenPath: "/",
			},
			CustomMiddleware: apidef.MiddlewareSection{
				Driver: apidef.GoPluginDriver,
				Post: []apidef.MiddlewareDefinition{
					{
						Name: "Post",
						Path: "/path/to/plugin",
					},
				},
			},
			VersionData: apidef.VersionData{
				NotVersioned: true,
				Versions:     map[string]apidef.VersionInfo{},
			},
			ConfigDataDisabled: true,
		}
		migratedAPI, _, err := MigrateAndFillOAS(&api)
		assert.NoError(t, err)

		expectedPrePlugin := CustomPlugins{
			{
				Enabled:      true,
				FunctionName: "Post",
				Path:         "/path/to/plugin",
			},
		}
		assert.Equal(t, expectedPrePlugin, migratedAPI.OAS.GetTykExtension().Middleware.Global.PostPlugins)
		assert.Nil(t, migratedAPI.OAS.GetTykExtension().Middleware.Global.PostPlugin)
		assert.Equal(t, apidef.GoPluginDriver, migratedAPI.OAS.GetTykExtension().Middleware.Global.PluginConfig.Driver)
	})

	t.Run("response", func(t *testing.T) {
		t.Parallel()
		api := apidef.APIDefinition{
			Name: "Custom plugin - response",
			Proxy: apidef.ProxyConfig{
				ListenPath: "/",
			},
			CustomMiddleware: apidef.MiddlewareSection{
				Driver: apidef.GoPluginDriver,
				Response: []apidef.MiddlewareDefinition{
					{
						Name: "Response",
						Path: "/path/to/plugin",
					},
				},
			},
			VersionData: apidef.VersionData{
				NotVersioned: true,
				Versions:     map[string]apidef.VersionInfo{},
			},
			ConfigDataDisabled: true,
		}
		migratedAPI, _, err := MigrateAndFillOAS(&api)
		assert.NoError(t, err)

		expectedPrePlugin := CustomPlugins{
			{
				Enabled:      true,
				FunctionName: "Response",
				Path:         "/path/to/plugin",
			},
		}
		assert.Equal(t, expectedPrePlugin, migratedAPI.OAS.GetTykExtension().Middleware.Global.ResponsePlugins)
		assert.Nil(t, migratedAPI.OAS.GetTykExtension().Middleware.Global.ResponsePlugin)
		assert.Equal(t, apidef.GoPluginDriver, migratedAPI.OAS.GetTykExtension().Middleware.Global.PluginConfig.Driver)
	})
}

func TestMigrateAndFillOAS_PluginConfigData(t *testing.T) {
	configData := map[string]interface{}{
		"key": "value",
	}

	api := apidef.APIDefinition{
		Name: "config data",
		Proxy: apidef.ProxyConfig{
			ListenPath: "/",
		},
		CustomMiddleware: apidef.MiddlewareSection{
			Driver: apidef.GoPluginDriver,
		},
		VersionData: apidef.VersionData{
			NotVersioned: true,
			Versions:     map[string]apidef.VersionInfo{},
		},
		ConfigData: configData,
	}
	migratedAPI, _, err := MigrateAndFillOAS(&api)
	assert.NoError(t, err)

	expectedPluginConfigData := &PluginConfigData{
		Enabled: true,
		Value:   configData,
	}
	assert.Equal(t, expectedPluginConfigData, migratedAPI.OAS.GetTykExtension().Middleware.Global.PluginConfig.Data)
}

func TestAPIContext_getValidationOptionsFromConfig(t *testing.T) {
	t.Parallel()

	t.Run("should return validation options", func(t *testing.T) {
		conf, err := config.New()
		assert.Nil(t, err)
		options := GetValidationOptionsFromConfig(conf.OAS)
		assert.Len(t, options, 2)
	})

	t.Run("should return default validation options", func(t *testing.T) {
		conf, err := config.New()
		assert.Nil(t, err)

		conf.OAS.ValidateSchemaDefaults = true
		conf.OAS.ValidateExamples = true

		options := GetValidationOptionsFromConfig(conf.OAS)

		assert.Len(t, options, 0)
	})
}

func TestOAS_Normalize(t *testing.T) {
	t.Run("should copy JWT validation fields correctly", func(t *testing.T) {
		oas := &OAS{
			T: openapi3.T{
				Security: openapi3.SecurityRequirements{
					{
						"jwt1": []string{},
					},
				},
				Components: &openapi3.Components{
					SecuritySchemes: openapi3.SecuritySchemes{
						"jwt1": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type:         typeHTTP,
								Scheme:       schemeBearer,
								BearerFormat: bearerFormatJWT,
							},
						},
					},
				},
			},
		}

		// Setup initial JWT configuration with new fields
		jwt := &JWT{
			BasePolicyClaims: []string{"policy_claim"},
			SubjectClaims:    []string{"subject_claim"},
			Scopes: &Scopes{
				Claims: []string{"scope_claim"},
			},
		}

		oas.SetTykExtension(&XTykAPIGateway{
			Server: Server{
				Authentication: &Authentication{
					SecuritySchemes: SecuritySchemes{
						"jwt1": jwt,
					},
				},
			},
		})

		// Call Normalize to copy values to old fields
		oas.Normalize()

		// Verify that old fields were populated from new fields
		jwtConfig := oas.GetJWTConfiguration()
		assert.Equal(t, "policy_claim", jwtConfig.PolicyFieldName)
		assert.Equal(t, "subject_claim", jwtConfig.IdentityBaseField)
		assert.Equal(t, "scope_claim", jwtConfig.Scopes.ClaimName)

		// Verify that new fields remain unchanged
		assert.Equal(t, []string{"policy_claim"}, jwtConfig.BasePolicyClaims)
		assert.Equal(t, []string{"subject_claim"}, jwtConfig.SubjectClaims)
		assert.Equal(t, []string{"scope_claim"}, jwtConfig.Scopes.Claims)

		t.Run("scopes nil", func(t *testing.T) {
			oas.GetJWTConfiguration().Scopes = nil

			oas.Normalize()

			assert.Equal(t, "policy_claim", jwtConfig.PolicyFieldName)
			assert.Equal(t, "subject_claim", jwtConfig.IdentityBaseField)
		})
	})
	t.Run("should be nil-safe", func(t *testing.T) {
		oas := &OAS{
			T: openapi3.T{
				Security: openapi3.SecurityRequirements{
					{
						"jwt1": []string{},
					},
				},
				Components: &openapi3.Components{
					SecuritySchemes: openapi3.SecuritySchemes{
						"jwt1": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type:         typeHTTP,
								Scheme:       schemeBearer,
								BearerFormat: bearerFormatJWT,
							},
						},
					},
				},
			},
		}

		// Setup initial JWT configuration with new fields
		jwt := &JWT{
			BasePolicyClaims: []string{"policy_claim"},
			SubjectClaims:    []string{"subject_claim"},
			Scopes: &Scopes{
				Claims: []string{"scope_claim"},
			},
		}

		// use wrong id to trigger null jwt
		oas.SetTykExtension(&XTykAPIGateway{
			Server: Server{
				Authentication: &Authentication{
					SecuritySchemes: SecuritySchemes{
						"jwt2": jwt,
					},
				},
			},
		})

		// Call Normalize to copy values to old fields
		oas.Normalize()

		// Verify that old fields were populated from new fields
		jwtConfig := oas.GetJWTConfiguration()
		assert.Nil(t, jwtConfig)
	})

}

func TestOAS_ValidateSecurity(t *testing.T) {
	apiKey := "api_key"
	oauth2 := "oauth2"

	createSecurityReq := func(oas *OAS, key string, value []string) {
		securityRequirement := openapi3.NewSecurityRequirement()
		securityRequirement[key] = value
		oas.Security = append(oas.Security, securityRequirement)
	}

	addSingleSecurityReq := func(oas *OAS) {
		createSecurityReq(oas, apiKey, []string{})
	}

	addMultipleSecurityReq := func(oas *OAS) {
		addSingleSecurityReq(oas)
		createSecurityReq(oas, oauth2, []string{"read", "write"})
	}

	expectedErrorForNoComponentOrSchemes := "No components or security schemes present in OAS"
	expectedErrorForMissingSchema := func(s string) string {
		return fmt.Sprintf("Missing required Security Scheme '%s' in Components.SecuritySchemes", s)
	}

	tests := []struct {
		name          string
		setupOAS      func(oas *OAS)
		expectedError string
	}{
		{
			name: "no security requirements",
			setupOAS: func(oas *OAS) {
				oas.Security = openapi3.SecurityRequirements{}
			},
			expectedError: "",
		},
		{
			name: "security requirements with matching security schemes",
			setupOAS: func(oas *OAS) {
				addSingleSecurityReq(oas)

				if oas.Components == nil {
					oas.Components = &openapi3.Components{}
				}
				if oas.Components.SecuritySchemes == nil {
					oas.Components.SecuritySchemes = make(openapi3.SecuritySchemes)
				}
				oas.Components.SecuritySchemes[apiKey] = &openapi3.SecuritySchemeRef{
					Value: openapi3.NewJWTSecurityScheme(),
				}
			},
			expectedError: "",
		},
		{
			name: "security requirements but no Components",
			setupOAS: func(oas *OAS) {
				addSingleSecurityReq(oas)

				oas.Components = nil
			},
			expectedError: expectedErrorForNoComponentOrSchemes,
		},
		{
			name: "security requirements but no SecuritySchemes",
			setupOAS: func(oas *OAS) {
				addSingleSecurityReq(oas)

				// Add Components but not SecuritySchemes
				oas.Components = &openapi3.Components{}
				oas.Components.SecuritySchemes = nil
			},
			expectedError: expectedErrorForNoComponentOrSchemes,
		},
		{
			name: "security requirements but empty SecuritySchemes",
			setupOAS: func(oas *OAS) {
				addSingleSecurityReq(oas)

				oas.Components = &openapi3.Components{}
				oas.Components.SecuritySchemes = make(openapi3.SecuritySchemes)
			},
			expectedError: expectedErrorForNoComponentOrSchemes,
		},
		{
			name: "security requirements with missing security scheme for this requirement",
			setupOAS: func(oas *OAS) {
				addSingleSecurityReq(oas)

				if oas.Components == nil {
					oas.Components = &openapi3.Components{}
				}
				if oas.Components.SecuritySchemes == nil {
					oas.Components.SecuritySchemes = make(openapi3.SecuritySchemes)
				}
				oas.Components.SecuritySchemes["other_key"] = &openapi3.SecuritySchemeRef{
					Value: openapi3.NewJWTSecurityScheme(),
				}
			},
			expectedError: expectedErrorForMissingSchema(apiKey),
		},
		{
			name: "multiple security requirements with all matching security schemes",
			setupOAS: func(oas *OAS) {
				addMultipleSecurityReq(oas)

				if oas.Components == nil {
					oas.Components = &openapi3.Components{}
				}
				if oas.Components.SecuritySchemes == nil {
					oas.Components.SecuritySchemes = make(openapi3.SecuritySchemes)
				}
				oas.Components.SecuritySchemes[apiKey] = &openapi3.SecuritySchemeRef{
					Value: openapi3.NewJWTSecurityScheme(),
				}
				oas.Components.SecuritySchemes[oauth2] = &openapi3.SecuritySchemeRef{
					Value: openapi3.NewJWTSecurityScheme(),
				}
			},
			expectedError: "",
		},
		{
			name: "multiple security requirements with one missing security scheme",
			setupOAS: func(oas *OAS) {
				addMultipleSecurityReq(oas)

				if oas.Components == nil {
					oas.Components = &openapi3.Components{}
				}
				if oas.Components.SecuritySchemes == nil {
					oas.Components.SecuritySchemes = make(openapi3.SecuritySchemes)
				}
				oas.Components.SecuritySchemes[apiKey] = &openapi3.SecuritySchemeRef{}
			},
			expectedError: expectedErrorForMissingSchema(oauth2),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			oas := &OAS{
				T: openapi3.T{
					OpenAPI: "3.0.3",
					Info: &openapi3.Info{
						Title:   "Test API",
						Version: "1.0.0",
					},
					Paths: openapi3.NewPaths(),
				},
			}

			tt.setupOAS(oas)

			err := oas.Validate(context.Background())
			if tt.expectedError == "" {
				assert.NoError(t, err)
			} else {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
			}
		})
	}
}

func TestOAS_ValidateCompliantModeAuthentication(t *testing.T) {
	t.Parallel()

	enabledTrue := true

	tests := []struct {
		name          string
		setupOAS      func(oas *OAS)
		expectedError string
	}{
		{
			name: "no authentication - should pass",
			setupOAS: func(_ *OAS) {
				// No authentication configured
			},
			expectedError: "",
		},
		{
			name: "legacy mode with enabled auth not in security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeLegacy,
							SecuritySchemes: SecuritySchemes{
								"jwt": &JWT{Enabled: true},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with enabled JWT in OAS security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"jwt": &JWT{Enabled: true},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)

				// Add JWT to OAS security and components
				oas.T.Components = &openapi3.Components{
					SecuritySchemes: openapi3.SecuritySchemes{
						"jwt": &openapi3.SecuritySchemeRef{
							Value: openapi3.NewJWTSecurityScheme(),
						},
					},
				}
				secReq := openapi3.NewSecurityRequirement()
				secReq["jwt"] = []string{}
				oas.T.Security = openapi3.SecurityRequirements{secReq}
			},
			expectedError: "",
		},
		{
			name: "compliant mode with enabled HMAC in vendor security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"hmac": &HMAC{Enabled: true},
							},
							Security: [][]string{{"hmac"}},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with top-level HMAC enabled in vendor security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							HMAC:                   &HMAC{Enabled: true},
							Security:               [][]string{{"hmac"}},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with top-level Custom enabled in vendor security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							Custom:                 &CustomPluginAuthentication{Enabled: true},
							Security:               [][]string{{"custom"}},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with top-level OIDC enabled in vendor security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							OIDC:                   &OIDC{Enabled: true},
							Security:               [][]string{{"oidc"}},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with enabled custom auth in vendor security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"customAuth": &CustomPluginAuthentication{Enabled: true},
							},
							Security: [][]string{{"customAuth"}},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with enabled Token in vendor security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"authToken": &Token{Enabled: &enabledTrue},
							},
							Security: [][]string{{"authToken"}},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with multiple enabled auth in security - should pass",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"jwt":    &JWT{Enabled: true},
								"hmac":   &HMAC{Enabled: true},
								"custom": &CustomPluginAuthentication{Enabled: true},
							},
							Security: [][]string{{"hmac"}, {"custom"}},
						},
					},
				}
				oas.SetTykExtension(xTykExt)

				// Add JWT to OAS security and components
				oas.T.Components = &openapi3.Components{
					SecuritySchemes: openapi3.SecuritySchemes{
						"jwt": &openapi3.SecuritySchemeRef{
							Value: openapi3.NewJWTSecurityScheme(),
						},
					},
				}
				secReq := openapi3.NewSecurityRequirement()
				secReq["jwt"] = []string{}
				oas.T.Security = openapi3.SecurityRequirements{secReq}
			},
			expectedError: "",
		},
		{
			name: "compliant mode with enabled JWT not in any security - should fail",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"jwt": &JWT{Enabled: true},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "invalid multi-auth configuration: jwt auth enabled but not configured in a security requirement",
		},
		{
			name: "compliant mode with enabled HMAC not in vendor security - should fail",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"hmac": &HMAC{Enabled: true},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "invalid multi-auth configuration: hmac auth enabled but not configured in a security requirement",
		},
		{
			name: "compliant mode with top-level HMAC enabled not in vendor security - should fail",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							HMAC:                   &HMAC{Enabled: true},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "invalid multi-auth configuration: hmac auth enabled but not configured in a security requirement",
		},
		{
			name: "compliant mode with top-level Custom enabled not in vendor security - should fail",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							Custom:                 &CustomPluginAuthentication{Enabled: true},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "invalid multi-auth configuration: custom auth enabled but not configured in a security requirement",
		},
		{
			name: "compliant mode with top-level OIDC enabled not in vendor security - should fail",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							OIDC:                   &OIDC{Enabled: true},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "invalid multi-auth configuration: oidc auth enabled but not configured in a security requirement",
		},
		{
			name: "compliant mode with enabled custom auth not in vendor security - should fail",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"customAuth": &CustomPluginAuthentication{Enabled: true},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "invalid multi-auth configuration: customAuth auth enabled but not configured in a security requirement",
		},
		{
			name: "compliant mode with multiple enabled auth not configured - should fail with both",
			setupOAS: func(oas *OAS) {
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"hmac":   &HMAC{Enabled: true},
								"custom": &CustomPluginAuthentication{Enabled: true},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "auth methods enabled but not configured in a security requirement",
		},
		{
			name: "compliant mode with disabled auth not in security - should pass",
			setupOAS: func(oas *OAS) {
				enabledFalse := false
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"jwt":   &JWT{Enabled: false},
								"token": &Token{Enabled: &enabledFalse},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "",
		},
		{
			name: "compliant mode with mixed enabled and disabled auth - should fail for enabled only",
			setupOAS: func(oas *OAS) {
				enabledFalse := false
				xTykExt := &XTykAPIGateway{
					Server: Server{
						Authentication: &Authentication{
							SecurityProcessingMode: SecurityProcessingModeCompliant,
							SecuritySchemes: SecuritySchemes{
								"jwt":   &JWT{Enabled: true},
								"token": &Token{Enabled: &enabledFalse},
							},
						},
					},
				}
				oas.SetTykExtension(xTykExt)
			},
			expectedError: "invalid multi-auth configuration: jwt auth enabled but not configured in a security requirement",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			oas := &OAS{
				T: openapi3.T{
					OpenAPI: "3.0.3",
					Info: &openapi3.Info{
						Title:   "Test API",
						Version: "1.0.0",
					},
					Paths: openapi3.NewPaths(),
				},
			}

			tt.setupOAS(oas)

			err := oas.Validate(context.Background())
			if tt.expectedError == "" {
				assert.NoError(t, err)
			} else {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
			}
		})
	}
}

func TestYaml(t *testing.T) {
	oasDoc := OAS{}
	Fill(t, &oasDoc, 0)

	tykExt := XTykAPIGateway{}
	Fill(t, &tykExt, 0)
	// json unmarshal workarounds
	{
		tykExt.Info.DBID = model.NewObjectID()
		tykExt.Middleware.Global.PrePlugin = nil
		tykExt.Middleware.Global.PostPlugin = nil
		tykExt.Middleware.Global.PostAuthenticationPlugin = nil
		tykExt.Middleware.Global.ResponsePlugin = nil

		for k, v := range tykExt.Server.Authentication.SecuritySchemes {
			intVal, ok := v.(int)
			assert.True(t, ok)
			tykExt.Server.Authentication.SecuritySchemes[k] = float64(intVal)
		}

		for k, v := range tykExt.Middleware.Global.PluginConfig.Data.Value {
			intVal, ok := v.(int)
			assert.True(t, ok)
			tykExt.Middleware.Global.PluginConfig.Data.Value[k] = float64(intVal)
		}
	}

	oasDoc.SetTykExtension(&tykExt)

	jsonBody, err := json.Marshal(&oasDoc)
	assert.NoError(t, err)

	yamlBody, err := yaml.JSONToYAML(jsonBody)
	assert.NoError(t, err)

	yamlOAS, err := openapi3.NewLoader().LoadFromData(yamlBody)
	assert.NoError(t, err)

	yamlOASDoc := OAS{
		T: *yamlOAS,
	}

	yamlOASExt := yamlOASDoc.GetTykExtension()
	assert.Equal(t, tykExt, *yamlOASExt)

	yamlOASDoc.SetTykExtension(nil)
	oasDoc.SetTykExtension(nil)
	assert.Equal(t, oasDoc, yamlOASDoc)
}

func TestOAS_Initialize(t *testing.T) {
	t.Parallel()

	t.Run("should initialize all security schemes", func(t *testing.T) {
		t.Parallel()

		// Create OAS with various security schemes
		oasDoc := &OAS{
			T: openapi3.T{
				Components: &openapi3.Components{
					SecuritySchemes: openapi3.SecuritySchemes{
						"jwt": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type:         typeHTTP,
								Scheme:       schemeBearer,
								BearerFormat: bearerFormatJWT,
							},
						},
						"basic": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type:   typeHTTP,
								Scheme: schemeBasic,
							},
						},
						"apiKey": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type: typeAPIKey,
								In:   "header",
								Name: "X-API-Key",
							},
						},
						"oauth": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type: typeOAuth2,
								Flows: &openapi3.OAuthFlows{
									AuthorizationCode: &openapi3.OAuthFlow{
										AuthorizationURL: "/oauth/authorize",
										TokenURL:         "/oauth/token",
										Scopes:           map[string]string{},
									},
								},
							},
						},
					},
				},
			},
		}

		// Set up Tyk extension with security schemes as map[string]interface{}
		// This simulates what happens when JSON is unmarshalled
		xTykAPIGateway := &XTykAPIGateway{
			Server: Server{
				Authentication: &Authentication{
					SecuritySchemes: SecuritySchemes{
						"jwt": map[string]interface{}{
							"enabled": true,
							"source":  "test-source",
						},
						"basic": map[string]interface{}{
							"enabled": true,
						},
						"apiKey": map[string]interface{}{
							"enabled": true,
						},
						"oauth": map[string]interface{}{
							"enabled": true,
						},
					},
				},
			},
		}
		oasDoc.SetTykExtension(xTykAPIGateway)

		// Call Initialize
		oasDoc.Initialize()

		// Verify that all security schemes have been converted to their proper types
		securitySchemes := oasDoc.getTykSecuritySchemes()

		// JWT should be converted to *JWT
		jwt, ok := securitySchemes["jwt"].(*JWT)
		assert.True(t, ok, "JWT should be converted to *JWT type")
		assert.True(t, jwt.Enabled)

		// Basic should be converted to *Basic
		basic, ok := securitySchemes["basic"].(*Basic)
		assert.True(t, ok, "Basic should be converted to *Basic type")
		assert.True(t, basic.Enabled)

		// Token should be converted to *Token
		token, ok := securitySchemes["apiKey"].(*Token)
		assert.True(t, ok, "Token should be converted to *Token type")
		assert.NotNil(t, token.Enabled)
		assert.True(t, *token.Enabled)

		// OAuth should be converted to *OAuth
		oauth, ok := securitySchemes["oauth"].(*OAuth)
		assert.True(t, ok, "OAuth should be converted to *OAuth type")
		assert.True(t, oauth.Enabled)
	})

	t.Run("should handle nil components gracefully", func(t *testing.T) {
		t.Parallel()

		oasDoc := &OAS{
			T: openapi3.T{
				Components: nil,
			},
		}

		// Should not panic
		oasDoc.Initialize()
	})

	t.Run("should handle nil security schemes gracefully", func(t *testing.T) {
		t.Parallel()

		oasDoc := &OAS{
			T: openapi3.T{
				Components: &openapi3.Components{
					SecuritySchemes: nil,
				},
			},
		}

		// Should not panic
		oasDoc.Initialize()
	})

	t.Run("should handle external OAuth", func(t *testing.T) {
		t.Parallel()

		oasDoc := &OAS{
			T: openapi3.T{
				Components: &openapi3.Components{
					SecuritySchemes: openapi3.SecuritySchemes{
						"externalOAuth": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type: typeOAuth2,
								Flows: &openapi3.OAuthFlows{
									AuthorizationCode: &openapi3.OAuthFlow{
										AuthorizationURL: "/oauth/authorize",
										TokenURL:         "/oauth/token",
										Scopes:           map[string]string{},
									},
								},
							},
						},
					},
				},
			},
		}

		// Set up Tyk extension with external OAuth (has providers)
		xTykAPIGateway := &XTykAPIGateway{
			Server: Server{
				Authentication: &Authentication{
					SecuritySchemes: SecuritySchemes{
						"externalOAuth": map[string]interface{}{
							"enabled": true,
							"providers": []interface{}{
								map[string]interface{}{
									"jwt": map[string]interface{}{
										"enabled": true,
									},
								},
							},
						},
					},
				},
			},
		}
		oasDoc.SetTykExtension(xTykAPIGateway)

		// Call Initialize
		oasDoc.Initialize()

		// Verify external OAuth is converted
		securitySchemes := oasDoc.getTykSecuritySchemes()
		externalOAuth, ok := securitySchemes["externalOAuth"].(*ExternalOAuth)
		assert.True(t, ok, "ExternalOAuth should be converted to *ExternalOAuth type")
		assert.True(t, externalOAuth.Enabled)
		assert.Len(t, externalOAuth.Providers, 1)
	})

	t.Run("concurrent access should not cause panic", func(t *testing.T) {
		t.Parallel()

		// Create OAS with JWT security scheme
		oasDoc := &OAS{
			T: openapi3.T{
				Components: &openapi3.Components{
					SecuritySchemes: openapi3.SecuritySchemes{
						"jwt": &openapi3.SecuritySchemeRef{
							Value: &openapi3.SecurityScheme{
								Type:         typeHTTP,
								Scheme:       schemeBearer,
								BearerFormat: bearerFormatJWT,
							},
						},
					},
				},
			},
		}

		xTykAPIGateway := &XTykAPIGateway{
			Server: Server{
				Authentication: &Authentication{
					SecuritySchemes: SecuritySchemes{
						"jwt": map[string]interface{}{
							"enabled": true,
							"source":  "test-source",
						},
					},
				},
			},
		}
		oasDoc.SetTykExtension(xTykAPIGateway)

		// Call Initialize first to properly initialize
		oasDoc.Initialize()

		// Now simulate concurrent access (which should be safe after initialization)
		done := make(chan bool)
		for i := 0; i < 100; i++ {
			go func() {
				defer func() {
					done <- true
				}()
				// After Initialize, these calls should just return the cached value
				_ = oasDoc.getTykJWTAuth("jwt")
			}()
		}

		// Wait for all goroutines to complete
		for i := 0; i < 100; i++ {
			<-done
		}
	})
}

func Test_RemoveServer(t *testing.T) {
	createOas := func() *OAS {
		var spec openapi3.T

		spec.OpenAPI = "3.0.3"
		spec.Info = &openapi3.Info{
			Title:   "Test API",
			Version: "1.0.0",
		}

		spec.Servers = append(spec.Servers, &openapi3.Server{
			URL: "https://{sub}.example.com/{version}",
			Variables: map[string]*openapi3.ServerVariable{
				"version": {
					Default: "v1",
				},
				"sub": {
					Default: "default",
				},
			},
		})

		return &OAS{spec}
	}

	t.Run("removes without fail", func(t *testing.T) {
		spec := createOas()
		err := spec.RemoveServer("https://{sub:[a-z]+}.example.com/{version:[0-9]+}")
		assert.NoError(t, err)
		assert.Len(t, spec.Servers, 0)
	})

	t.Run("does no remove no one server if server url is empty", func(t *testing.T) {
		spec := createOas()
		err := spec.RemoveServer("")
		assert.NoError(t, err)
		assert.Len(t, spec.Servers, 1)
	})

	t.Run("does not fail if given server does not exist", func(t *testing.T) {
		spec := createOas()
		err := spec.RemoveServer("https://example.com")
		assert.NoError(t, err)
		assert.Len(t, spec.Servers, 1)
	})

	t.Run("fails if invalid regex was provided", func(t *testing.T) {
		spec := createOas()
		err := spec.RemoveServer("https://{sub:[a-z]+}.example.com/{version:[0-9]+}}")
		assert.Error(t, err)
	})
}
